package com.gitee.springboot.firstlesson.basic.service;

import com.gitee.springboot.firstlesson.basic.dto.Dept;
import com.gitee.springboot.firstlesson.basic.dto.User;
import com.gitee.springboot.firstlesson.basic.repository.DeptReposity;
import com.gitee.springboot.firstlesson.basic.repository.UserReposity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author guoxuehe
 * @date 2019-06-20 15:50
 * @desc com.gitee.springboot.firstlesson.basic.service
 */
@Service
public class DeptService {

    @Autowired(required = false)
    private UserReposity userReposity;

    @Autowired(required = false)
    private DeptReposity deptReposity;

    /**
     * 只加 @Transactional 注解的时候 手动抛出 new Exception异常 会发现虽然前台抛出了异常,但是后台数据库实际是插入成功的.
     * 如果手动抛出了RuntimeException异常,则会发现后台数据库未插入成功
     * 说明 @Transactional 这个只会对非受检异常进行处理,对受检异常不处理
     * 如果需要对Exception的异常也进行捕获,需要在@Transactional 加上 rollbackFor = Exception.class
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public void insertUser() throws Exception {
        User user = new User("userA","人员A");
        userReposity.save(user);
        this.insertDept();
        throw new Exception("手动定义异常");
        //throw new RuntimeException("手动定义运行时异常");
    }

    /**
     * Spring事务传播性
     * Propagation.REQUIRED 如果当前没有事务,就新建一个事务,如果存在一个事务,加入到这个事务中.
     * 在 insertUser1()还是insertDept()方法中任一方法中添加 1/0 这段代码都会导致事务回滚.因为Propagation.REQUIRED
     * 表示将这两个方法放在一个事务中执行.
     * 如果异常在insertDept()中,但是在insertUser1()的时候把异常 try catch到的话,则但是在insertUser1正常执行,insertDept不会保存.
     * 如果在insertDept()上添加了@Transactional(propagation = Propagation.REQUIRED),在insertUser1()方法不添加事务,
     * 则insertDept()是一个事务,在insertUser1()并不是一个事务
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertUser1(){
        User user = new User("userB","人员B");
        userReposity.save(user);
        try {
            this.insertDept();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public void insertDept() throws Exception{
        int j = 1/0;
        Dept dept = new Dept("deptA","部门A");
        deptReposity.save(dept);
//        throw new Exception("添加部门异常");
    }

    /**
     * propagation = Propagation.SUPPORTS
     * 支持当前事务,如果当前没有事务,就以非事务方式执行.
     * 关于这个属性,在代码中主要是添加到insertDept21()上的,也就是被调用方法上.因为添加到insertUser2()就不以事务的方式运行了.
     * 然后,如果insertUser2()是事务,则insertDept1()也是事务,如果insertUser2()不是事务,则insertDept2()也不是事务
     */
    @Transactional(propagation=Propagation.REQUIRED)
    public void insertUser2(){
        User user = new User("userC","人员C");
        userReposity.save(user);
        insertDept2();
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public void insertDept2(){
        Dept dept = new Dept("deptC","部门C");
        deptReposity.save(dept);
        int i = 1/0;
    }

    /**
     * propagation = Propagation.MANDATORY
     * 使用当前的事务,如果当前没有事务,就抛出异常
     * 这个属性也是添加到被调用者方法上insertDept3(),如果加到insertUser3()调用者上,则直接抛出异常.
     * 该属性添加到insertDept3()上,如果insertUser3()有事务,则insertDept3()加入到insertUser3()的事务中,
     * 如果insertUser3()没有事务,则直接抛出异常.
     */
   // @Transactional(propagation=Propagation.REQUIRED)
   // @Transactional(propagation = Propagation.MANDATORY)
    public void insertUser3(){
        User user = new User("userC","人员C");
        userReposity.save(user);
        insertDept3();
        int i= 1/0;
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void insertDept3(){
        Dept dept = new Dept("deptC","部门C");
        deptReposity.save(dept);
        int i = 1/0;
    }

    /**
     * propagation=Propagation.REQUIRES_NEW
     * 这个属性也是针对被调用者insertDept4()方法的,不管调用者insertUser4()方法是否存在事务,被调用者都会新开一个事务,相当于
     * 被调用者都存在于自己的事务中和调用者没有关系.
     * 在下述代码中,insertUser4()会回滚,insertDept4()不会回滚,因为它们属于两个事务
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void insertUser4(){
        User user = new User("userC","人员C");
        userReposity.save(user);
        insertDept4();
        int i= 1/0;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertDept4(){
        //int i = 1/0;
        Dept dept = new Dept("deptC","部门C");
        deptReposity.save(dept);

    }

    /**
     * Propagation.NOT_SUPPORTED
     * 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
     * 这段代码如果放在调用者insertUser5()方法上,则是以非事务方式运行.
     * 如果放在被调用者insertDept5()方法上,该方法以非事务运行,调用者如果有事务,则运行单独的事务(挂起).
     * 在下述代码中,会出现insertUser5()回滚,insertDept5()不回滚.
     */
    @Transactional(propagation=Propagation.REQUIRED)
    public void insertUser5(){
        User user = new User("userC","人员C");
        userReposity.save(user);
        insertDept5();
        int i= 1/0;
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void insertDept5(){

        Dept dept = new Dept("deptC","部门C");
        deptReposity.save(dept);
        int i = 1/0;
    }

    /**
     * propagation = Propagation.NEVER
     * 以非事务方式执行,如果当前存在事务,则抛出异常.
     * 如果在调用者上,则直接以非事务运行.如果作用在被调用者上,则看调用者是否存在事务,如果调用者有事务,则抛出异常;如果没有事务,
     * 则以非事务运行
     * 在下述代码中,则会抛出异常 (Existing transaction found for transaction marked with propagation 'never')
     */
    @Transactional(propagation=Propagation.REQUIRED)
    public void insertUser6(){
        User user = new User("userC","人员C");
        userReposity.save(user);
        insertDept6();
        int i= 1/0;
    }

    @Transactional(propagation = Propagation.NEVER)
    public void insertDept6(){

        Dept dept = new Dept("deptC","部门C");
        deptReposity.save(dept);
        int i = 1/0;
    }

    /**
     * propagation = Propagation.NESTED
     * 如果当前存在事务,则在嵌套事务内执行.如果当前没有事务,则执行与propagation=Propagation.REQUIRED类似的操作
     * 这个和 propagation=Propagation.REQUIRED 的区别是 一个是加入到另一个事务中,一个是嵌套事务运行
     *
     * 可以这么理解，大多数情况下，效果和REQUIRED一样。但是有一种情况，就是上述代码中，被调用者事务传播属性为NESTED，当出现异常时， 调用者把departmentService try，catch了。这个区别与REQUIRED的并不会报错，而且addEmpByNested方法不会回滚，只有addDeptByNested回滚了。
     * 大概原理：当被调用者使用PROPAGATION_NESTED时，底层的数据源必须基于JDBC 3.0，并且实现者需要支持保存点事务机制。按上述代码来说（去掉try，catch），当执行到addDeptByNested这个方法时，Spring会为它创建一个内部的嵌套事务，如果addDeptByNested执行失败，则事务回滚到addDeptByNested之前的点，此时如果不抛异常，则不会回滚。这个嵌套事务，是addEmpByNested的一部分，只有外层事务提交了，内层的嵌套事务才会一起提交，这也是与REQUIRED（加入事务），REQUIRES_NEW（开启新事务）的区别。
     * 还值得一提的是，NESTED是嵌套的意思，其实并不是只有NESTED用于嵌套，只要理解上述7个传播机制的意思，都可以嵌套用
     */
    @Transactional(propagation=Propagation.REQUIRED)
    public void insertUser7(){
        User user = new User("userC","人员C");
        userReposity.save(user);
        insertDept7();
        int i= 1/0;
    }

    @Transactional(propagation = Propagation.NESTED)
    public void insertDept7(){

        Dept dept = new Dept("deptC","部门C");
        deptReposity.save(dept);
        int i = 1/0;
    }
}
